package ferp.core.player;

import ferp.core.card.Card;
import ferp.core.game.Game;
import ferp.core.game.Settings;
import ferp.core.game.Trick;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * User: igorgok
 * Date: 5/30/11 4:13 PM
 */
public class Hand
{
  // get hash value containing only cards from the given suit not moved
  public static int only(int hash, Card.Suit suit) {return hash & (0xFF << suit.power());}
  // merge cards
  public static int merge(int[] hashes)
  {
    int merged = 0;

    for (int h : hashes)
      merged |= h;

    return merged;
  }
  // add all cards from the other set
  public static int add(int hash, int h2) {return hash | h2;}
  // add all cards from array of sets
  public static int add(int hash, int[] h2) {return hash | merge(h2);}
  // add all cards in the given suit from other hash value
  public static int add(int hash, Card.Suit suit, int h2) {return hash | only(h2, suit);}
  // add card
  public static int add(int hash, Card.Suit suit, Card.Rank rank) {return hash | Card.card(suit, rank).hash;}
  // add card
  public static int add(int hash, Card card) {return hash | card.hash;}
  // remove card hash
  public static int remove(int hash, int h2) {return hash & ~h2;}
  // add all cards from array of sets
  public static int remove(int hash, int[] h2) {return hash & ~merge(h2);}
  // remove the whole suit
  public static int remove(int hash, Card.Suit suit) {return hash & ~(0xFF << suit.power());}
  // check if contains cards
  public static boolean contains(int hash, int h2) {return (hash & h2) == h2;}
  // adjust hand: adds talon and removes drop
  public static int adjust(int hash, int[] talon, int[] drop) {return remove(add(hash, talon), drop);}
  
  // get the first card in the hand
  public static int first(int hash)
  {
    for (int bit = 0x80000000; bit != 0; bit >>>= 1)
      if ((hash & bit) != 0)
        return bit;
    
    // no cards
    return 0;
  }

  // get the last card in the hand
  public static int last(int hash)
  {
    for (int bit = 0x00000001; bit != 0; bit <<= 1)
      if ((hash & bit) != 0)
        return bit;

    // no cards
    return 0;
  }

  // get the next card in the hand
  public static int next(int hash, int current)
  {
    while ((current >>>= 1) != 0)
      if ((hash & current) != 0)
        return current;

    // no cards
    return 0;
  }

  // get the next card in the hand
  public static int previous(int hash, int current)
  {
    while ((current <<= 1) != 0)
      if ((hash & current) != 0)
        return current;

    // no cards
    return 0;
  }

  // get size of hand
  public static int size(int hash)
  {
    int size = 0;

    for (Card.Suit s : Card.Suit.rising)
      size += Card.Set.size(Card.Set.get(hash, s));

    return size;
  }

  // get number of suits
  public static int suits(int hash)
  {
    int count = 0;

    for (Card.Suit s : Card.Suit.rising)
      if (Hand.only(hash, s) != 0)
        ++count;

    return count;
  }

  // get potential candidates for move
  public static int getMoveCandidates(Game g, int hash) {return getMoveCandidates(hash, g.trick.current, g.trump());}
  public static int getMoveCandidates(int hash, int trick, Card.Suit trump)
  {
    // try to get the first played card, initially from the talon part of trick (set in passing mode)
    Card.Suit suit = Trick.suit(trick);

    if (suit != null)
    {
      // get cards set of the trick's suit
      int set = only(hash, suit);

      // we have the starting card here, try to return our set of cards in this suit or
      // if the trump is defined try to return trump suit cards
      if ((set != 0) || (trump != Card.Suit.NONE && (set = only(hash, trump)) != 0))
        return set;
    }

    // player is a starter or does not have cards of first card suit or of trump suit
    return hash;
  }

  // get all cards ordered by provided rank order
  public static List<Card> cards(int hash, ArrayList<Card.Suit> suits, Settings.RankOrder rankOrder, boolean sort)
  {
    ArrayList<Card> result = new ArrayList<Card>(Game.HAND_SIZE);
    Card.Rank[] ranks = rankOrder == Settings.RankOrder.ASCENDING ? Card.Rank.rising : Card.Rank.falling;

    for (Card.Suit s : suits)
      for (Card.Rank r : ranks)
      {
        Card c = Card.card(s, r);

        if (Hand.contains(hash, c.hash))
          result.add(c);
      }

    if (!sort)
      // Fisher–Yates - 7 times
      for (int p = 0; p < 7; ++p)
        for (int i = result.size() - 1; i > 0; --i)
          swap(result, i, random.nextInt(i + 1));

    return result;
  }

  public static void getOrderedSuits(int hash, Settings.SuitOrder order, ArrayList<Card.Suit> suits)
  {
    switch (order)
    {
      case COLOR:
        // refill the suits list in the following cases:
        //  1. for the first time - suits.size() == 0
        //  2. when talon taken - Hand.size(hash) >= Game.MAXIMUM_TRICKS
        //  3. drop thrown - Hand.size(hash) >= Game.MAXIMUM_TRICKS
        //  4. number of suits in hand more than 2 to prevent not needed card switching
        if (suits.size() == 0 || Hand.suits(hash) > 2 || Hand.size(hash) >= Game.MAXIMUM_TRICKS)
          fillSuits(hash, suits);

        break;

      case COLOR_WITHOUT_REORDERING:
        // refill the suits list in the following cases:
        //  1. for the first time - suits.size() == 0
        //  2. when talon taken - Hand.size(hash) >= Game.MAXIMUM_TRICKS
        //  3. drop thrown - Hand.size(hash) >= Game.MAXIMUM_TRICKS
        //  4. the previous order of the suits list was by rank - suits.equals(Card.Suit.risingAsList)
        if (suits.size() == 0 || suits.equals(Card.Suit.risingAsList) || Hand.size(hash) >= Game.MAXIMUM_TRICKS)
          fillSuits(hash, suits);

        break;

      case RANK:
        // always refill the suits list to ensure proper order (previous order could differ from the current one which changed via preferences)
        suits.clear();
        suits.addAll(Card.Suit.risingAsList);

        break;
    }
  }

/*
  // get the lowest card of specific suit in hand
  public static Card.Rank lowest(int hash, Card.Suit s)
  {
    int set = Card.Set.get(hash, s);

    if (set != 0)
      for (Card.Rank r : Card.Rank.rising)
        if (Card.Set.contains(set, r))
          return r;

    return null;
  }
*/

  public static String dump(int hash)
  {
    if (hash == 0)
      return "-";

    StringBuilder sb = new StringBuilder(128);

    for (Card.Suit s : Card.Suit.rising)
      dump(sb, s, hash);

    return sb.toString();
  }

  private static final Random random = new Random();

  private static final Card.Suit[][] suitsByColor =
  {
      Card.Suit.rising,                                                          //0
      {Card.Suit.SPADES},                                                        //1
      {Card.Suit.CLUBS},                                                         //2
      {Card.Suit.SPADES, Card.Suit.CLUBS},                                       //3
      {Card.Suit.DIAMONDS},                                                      //4
      {Card.Suit.SPADES, Card.Suit.DIAMONDS},                                    //5
      {Card.Suit.CLUBS, Card.Suit.DIAMONDS},                                     //6
      {Card.Suit.SPADES, Card.Suit.DIAMONDS, Card.Suit.CLUBS},                   //7
      {Card.Suit.HEARTS},                                                        //8
      {Card.Suit.SPADES, Card.Suit.HEARTS},                                      //9
      {Card.Suit.CLUBS, Card.Suit.HEARTS},                                       //10
      {Card.Suit.SPADES, Card.Suit.HEARTS, Card.Suit.CLUBS},                     //11
      {Card.Suit.DIAMONDS, Card.Suit.HEARTS},                                    //12
      {Card.Suit.DIAMONDS, Card.Suit.SPADES, Card.Suit.HEARTS},                  //13
      {Card.Suit.DIAMONDS, Card.Suit.CLUBS, Card.Suit.HEARTS},                   //14
      {Card.Suit.SPADES, Card.Suit.DIAMONDS, Card.Suit.CLUBS, Card.Suit.HEARTS}, //15
  };

  private static void dump(StringBuilder sb, Card.Suit s, int hash)
  {
    if ((hash = Card.Set.get(hash, s)) == 0)
      return;

    sb.append(' ').append(s).append(": ");
    Card.Set.dump(sb, hash);
  }

  private static void swap(ArrayList<Card> array, int i1, int i2)
  {
    Card c1 = array.get(i1);
    Card c2 = array.get(i2);

    array.set(i1, c2);
    array.set(i2, c1);
  }

  private static void fillSuits(int hash, ArrayList<Card.Suit> suits)
  {
    int index = 0;

    for (Card.Suit s : Card.Suit.rising)
      if (Hand.only(hash, s) != 0)
        index |= 1 << s.ordinal();

    suits.clear();

    for (Card.Suit s : suitsByColor[index])
      suits.add(s);
  }
}
